﻿
#include "websocketClient.h"
#include <codecvt>
/*
 * Copyright (c) 2014, Peter Thorson. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the WebSocket++ Project nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL PETER THORSON BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

 // **NOTE:** This file is a snapshot of the WebSocket++ utility client tutorial.
 // Additional related material can be found in the tutorials/utility_client
 // directory of the WebSocket++ repository.


//connect ws://localhost:9100

#include <websocketpp/config/asio_no_tls_client.hpp>
#include <websocketpp/client.hpp>

#include <websocketpp/common/thread.hpp>
#include <websocketpp/common/memory.hpp>

#include <cstdlib>
#include <iostream>
#include <map>
#include <string>
#include <sstream>

static std::wstring string_to_wstring(const std::string &s)
{
	using default_convert = std::codecvt<wchar_t, char, std::mbstate_t>;
	static std::wstring_convert<default_convert>conv(new default_convert("CHS"));
	return conv.from_bytes(s);
}
static std::string wstring_to_string(const std::wstring &s)
{
	using default_convert = std::codecvt<wchar_t, char, std::mbstate_t>;
	static std::wstring_convert<default_convert>conv(new default_convert("CHS"));
	return conv.to_bytes(s);
}
static std::string ansi_to_utf8(const std::string &s)
{
	static std::wstring_convert<std::codecvt_utf8<wchar_t> > conv;
	return conv.to_bytes(string_to_wstring(s));
}
static std::string utf8_to_ansi(const std::string& s)
{
	static std::wstring_convert<std::codecvt_utf8<wchar_t> > conv;
	return wstring_to_string(conv.from_bytes(s));
}

typedef websocketpp::client<websocketpp::config::asio_client> websocketClient;

class connection_metadata {
private:
	int m_id;
	websocketpp::connection_hdl m_hdl;
	std::string m_status;
	std::string m_uri;
	std::string m_server;
	std::string m_error_reason;
	std::vector<std::string> m_messages;
public:
	typedef websocketpp::lib::shared_ptr<connection_metadata> ptr;

	connection_metadata(int id, websocketpp::connection_hdl hdl, std::string uri)
		: m_id(id)
		, m_hdl(hdl)
		, m_status("Connecting")
		, m_uri(uri)
		, m_server("N/A")
	{}

	void on_open(websocketClient * c, websocketpp::connection_hdl hdl) {
		m_status = "Open";

		websocketClient::connection_ptr con = c->get_con_from_hdl(hdl);
		m_server = con->get_response_header("Server");
	}

	void on_fail(websocketClient * c, websocketpp::connection_hdl hdl) {
		m_status = "Failed";

		websocketClient::connection_ptr con = c->get_con_from_hdl(hdl);
		m_server = con->get_response_header("Server");
		m_error_reason = con->get_ec().message();
	}

	void on_close(websocketClient * c, websocketpp::connection_hdl hdl) {
		m_status = "Closed";
		websocketClient::connection_ptr con = c->get_con_from_hdl(hdl);
		std::stringstream s;
		s << "close code: " << con->get_remote_close_code() << " ("
			<< websocketpp::close::status::get_string(con->get_remote_close_code())
			<< "), close reason: " << con->get_remote_close_reason();
		m_error_reason = s.str();
	}

	void on_message(websocketpp::connection_hdl, websocketClient::message_ptr msg) {
		if (msg->get_opcode() == websocketpp::frame::opcode::text) {
			std::string message = utf8_to_ansi(msg->get_payload());
			std::cout << "收到来自服务器的消息：" << message << std::endl;
			m_messages.push_back("<< " + msg->get_payload());
		}
		else {
			std::cout << "收到来自服务器的消息binary：" << msg->get_payload() << std::endl;
			m_messages.push_back("<< " + websocketpp::utility::to_hex(msg->get_payload()));
		}
	}
	//收到Ping,则回复Pong（一般客户端去ping,收pong）
	bool on_ping(websocketClient * c, websocketpp::connection_hdl hdl, std::string msg) {
		//m_status = "Ping";
		//websocketClient::connection_ptr con = c->get_con_from_hdl(hdl);
		////pong,原样返回
		//con->pong(msg);
		return true;
	}

	//收到Pong(一般客户端去ping,收pong)
	void on_pong(websocketClient * c, websocketpp::connection_hdl hdl, std::string msg) {
		//m_status = "Pong";
		/*websocketClient::connection_ptr con = c->get_con_from_hdl(hdl);
		con->ping(msg);*/
		std::cout << "Pong handler: " << msg << std::endl;
	}
	websocketpp::connection_hdl get_hdl() const {
		return m_hdl;
	}

	int get_id() const {
		return m_id;
	}

	std::string get_status() const {
		return m_status;
	}

	void record_sent_message(std::string message) {
		m_messages.push_back(">> " + message);
	}

	friend std::ostream & operator<< (std::ostream & out, connection_metadata const & data);

};

std::ostream & operator<< (std::ostream & out, connection_metadata const & data) {
	out << "> URI: " << data.m_uri << "\n"
		<< "> Status: " << data.m_status << "\n"
		<< "> Remote Server: " << (data.m_server.empty() ? "None Specified" : data.m_server) << "\n"
		<< "> Error/close reason: " << (data.m_error_reason.empty() ? "N/A" : data.m_error_reason) << "\n";
	out << "> Messages Processed: (" << data.m_messages.size() << ") \n";

	std::vector<std::string>::const_iterator it;
	for (it = data.m_messages.begin(); it != data.m_messages.end(); ++it) {
		out << *it << "\n";
	}

	return out;
}

class websocket_endpoint {
private:
	typedef std::map<int, connection_metadata::ptr> con_list;

	websocketClient m_websocket_client;
	websocketpp::lib::shared_ptr<websocketpp::lib::thread> m_thread;

	con_list m_connection_list;
	int m_next_id;
public:
	websocket_endpoint() : m_next_id(0) {
		m_websocket_client.clear_access_channels(websocketpp::log::alevel::all);
		m_websocket_client.clear_error_channels(websocketpp::log::elevel::all);

		m_websocket_client.init_asio();
		m_websocket_client.start_perpetual();
		/*m_websocket_client.set_ping_handler
		m_websocket_client.set_pong_handler*/
		m_thread = websocketpp::lib::make_shared<websocketpp::lib::thread>(&websocketClient::run, &m_websocket_client);
	}

	~websocket_endpoint() {
		m_websocket_client.stop_perpetual();

		for (con_list::const_iterator it = m_connection_list.begin(); it != m_connection_list.end(); ++it) {
			if (it->second->get_status() != "Open") {
				// Only close open connections
				continue;
			}

			std::cout << "> Closing connection " << it->second->get_id() << std::endl;

			websocketpp::lib::error_code ec;
			m_websocket_client.close(it->second->get_hdl(), websocketpp::close::status::going_away, "", ec);
			if (ec) {
				std::cout << "> Error closing connection " << it->second->get_id() << ": "
					<< ec.message() << std::endl;
			}
		}

		m_thread->join();
	}

	int connect(std::string const & uri) {
		websocketpp::lib::error_code ec;
		//create connection
		websocketClient::connection_ptr con = m_websocket_client.get_connection(uri, ec);

		if (ec) {
			std::cout << "> Connect initialization error: " << ec.message() << std::endl;
			return -1;
		}

		int new_id = m_next_id++;
		//create connection_metadata
		connection_metadata::ptr metadata_ptr = websocketpp::lib::make_shared<connection_metadata>(new_id, con->get_handle(), uri);
		m_connection_list[new_id] = metadata_ptr;
		
		con->set_open_handler(websocketpp::lib::bind(
			&connection_metadata::on_open,
			metadata_ptr,
			&m_websocket_client,
			websocketpp::lib::placeholders::_1
		));

		con->set_fail_handler(websocketpp::lib::bind(
			&connection_metadata::on_fail,
			metadata_ptr,
			&m_websocket_client,
			websocketpp::lib::placeholders::_1
		));

		con->set_close_handler(websocketpp::lib::bind(
			&connection_metadata::on_close,
			metadata_ptr,
			&m_websocket_client,
			websocketpp::lib::placeholders::_1
		));

		con->set_ping_handler(websocketpp::lib::bind(
			&connection_metadata::on_ping,
			metadata_ptr,
			&m_websocket_client,
			websocketpp::lib::placeholders::_1,
			websocketpp::lib::placeholders::_2
		));

		con->set_pong_handler(websocketpp::lib::bind(
			&connection_metadata::on_pong,
			metadata_ptr,
			&m_websocket_client,
			websocketpp::lib::placeholders::_1,
			websocketpp::lib::placeholders::_2
		));

		con->set_message_handler(websocketpp::lib::bind(
			&connection_metadata::on_message,
			metadata_ptr,
			websocketpp::lib::placeholders::_1,
			websocketpp::lib::placeholders::_2
		));

		m_websocket_client.connect(con);
		/*con->ping("Ping");*/

		std::cout << "Websocket连接成功" << std::endl;
		return new_id;
	}

	void close(int id, websocketpp::close::status::value code, std::string reason) {
		websocketpp::lib::error_code ec;

		con_list::iterator metadata_it = m_connection_list.find(id);
		if (metadata_it == m_connection_list.end()) {
			std::cout << "> No connection found with id " << id << std::endl;
			return;
		}

		m_websocket_client.close(metadata_it->second->get_hdl(), code, reason, ec);
		if (ec) {
			std::cout << "> Error initiating close: " << ec.message() << std::endl;
		}
		std::cout << "关闭Websocket连接成功" << std::endl;
	}

	void send(int id, const std::string message) {
		websocketpp::lib::error_code ec;

		con_list::iterator metadata_it = m_connection_list.find(id);
		if (metadata_it == m_connection_list.end()) {
			std::cout << "> No connection found with id " << id << std::endl;
			return;
		}

		/*if (metadata_it->second->get_status() != "Open") {
			std::cout << "> connection not connected ok,waiting... " << id << std::endl;
			return;
		}*/
		m_websocket_client.send(metadata_it->second->get_hdl(), ansi_to_utf8(message), websocketpp::frame::opcode::text, ec);
		if (ec) {
			std::cout << "> Error sending message: " << ec.message() << std::endl;
			return;
		}

		metadata_it->second->record_sent_message(message);
	}

	void sendBinary(int id, const char* bytes) {
		websocketpp::lib::error_code ec;

		con_list::iterator metadata_it = m_connection_list.find(id);
		if (metadata_it == m_connection_list.end()) {
			std::cout << "> No connection found with id " << id << std::endl;
			return;
		}

		/*if (metadata_it->second->get_status() != "Open") {
			std::cout << "> connection not connected ok,waiting... " << id << std::endl;
			return;
		}*/
		m_websocket_client.send(metadata_it->second->get_hdl(),bytes, websocketpp::frame::opcode::binary, ec);
		if (ec) {
			std::cout << "> Error sending message: " << ec.message() << std::endl;
			return;
		}

	}

	connection_metadata::ptr get_metadata(int id) const {
		con_list::const_iterator metadata_it = m_connection_list.find(id);
		if (metadata_it == m_connection_list.end()) {
			return connection_metadata::ptr();
		}
		else {
			return metadata_it->second;
		}
	}
	
};

#define FRAME_SIZE 320
#define SAMPLE_RATE 8000

unsigned int getSendAudioSleepTime(const int dataSize,
	const int sampleRate,
	const int compressRate) {
	// 仅支持16位采样
	const int sampleBytes = 16;
	// 仅支持单通道
	const int soundChannel = 1;

	// 当前采样率，采样位数下每秒采样数据的大小
	int bytes = (sampleRate * sampleBytes * soundChannel) / 8;

	// 当前采样率，采样位数下每毫秒采样数据的大小
	int bytesMs = bytes / 1000;

	// 待发送数据大小除以每毫秒采样数据大小，以获取sleep时间
	int sleepMs = (dataSize * compressRate) / bytesMs;

	return sleepMs;
}

int main() {
	bool done = false;
	std::string input;
	websocket_endpoint endpoint;

	while (!done) {
		std::cout << "Enter Command: ";
		std::getline(std::cin, input);

		if (input == "quit") {
			done = true;
		}
		else if (input == "help") {
			std::cout
				<< "\nCommand List:\n"
				<< "connect <ws uri>\n"
				<< "send <connection id> <message>\n"
				<< "close <connection id> [<close code:default=1000>] [<close reason>]\n"
				<< "show <connection id>\n"
				<< "help: Display this help text\n"
				<< "quit: Exit the program\n"
				<< std::endl;
		}
		else if (input.substr(0, 7) == "connect") {
			int id = endpoint.connect(input.substr(8));
			if (id != -1) {
				std::cout << "> Created connection with id " << id << std::endl;
			}
		}
		else if (input.substr(0, 5) == "sendb") {

			std::stringstream ss(input);

			std::string cmd;
			int id;
			ss >> cmd >> id;

			const char* message;

			message = "1111ssss";

			/* 打开音频文件, 获取数据 */
			ifstream fs;
			string fileName = "I:\\projects\\hfzy-linkcall-svn\\web-linkcall-es\\src\\main\\resources\\static\\video\\12.wav";
			fs.open(fileName.c_str(), ios::binary | ios::in);
			if (!fs) {
				cout << fileName << " isn't exist.." << endl;
				return NULL;
			}

			int sleepMs = 0;

			// 文件是否读取完毕, 或者接收到TaskFailed, closed, completed回调, 终止send
			while ((!fs.eof())) {
				char data[FRAME_SIZE] = { 0 };

				fs.read(data, sizeof(char) * FRAME_SIZE);
				int nlen = (int)fs.gcount();

				/*
				* 4: 发送音频数据. 
				*/
				//nlen = request->sendAudio(data, nlen, false);
				cout << "send data hex:" << websocketpp::utility::to_hex(data) << endl;
				endpoint.sendBinary(id, data);
				if (nlen < 0) {
					// 发送失败, 退出循环数据发送
					cout << "send data fail." << endl;
					break;
				}
				else {
					cout << "send len:" << strlen(data) << " ." << endl;
				}

				/*
				*语音数据发送控制：
				*语音数据是实时的, 不用sleep控制速率, 直接发送即可.
				*语音数据来自文件, 发送时需要控制速率, 使单位时间内发送的数据大小接近单位时间原始语音数据存储的大小.
				*/
				sleepMs = getSendAudioSleepTime(nlen, SAMPLE_RATE, 1); // 根据 发送数据大小，采样率，数据压缩比 来获取sleep时间

				/*
				* 5: 语音数据发送延时控制
				*/
#ifdef _WIN32
				Sleep(sleepMs);
#else
				usleep(sleepMs * 1000);
#endif

			}
			// 关闭音频文件
			fs.close();
			std::cout << "> sendb success!" << std::endl;
		}
		else if (input.substr(0, 4) == "send") {
			std::stringstream ss(input);

			std::string cmd;
			int id;
			std::string message;

			ss >> cmd >> id;
			std::getline(ss, message);

			endpoint.send(id, message);
		}
		
		else if (input.substr(0, 5) == "close") {
			std::stringstream ss(input);

			std::string cmd;
			int id;
			int close_code = websocketpp::close::status::normal;
			std::string reason;

			ss >> cmd >> id >> close_code;
			std::getline(ss, reason);

			endpoint.close(id, close_code, reason);
		}
		else if (input.substr(0, 4) == "show") {
			int id = atoi(input.substr(5).c_str());

			connection_metadata::ptr metadata = endpoint.get_metadata(id);
			if (metadata) {
				std::cout << *metadata << std::endl;
			}
			else {
				std::cout << "> Unknown connection id " << id << std::endl;
			}
		}
		else {
			std::cout << "> Unrecognized Command" << std::endl;
		}
	}

	return 0;
}

/*

clang++ -std=c++11 -stdlib=libc++ -I/Users/zaphoyd/software/websocketpp/ -I/Users/zaphoyd/software/boost_1_55_0/ -D_WEBSOCKETPP_CPP11_STL_ step4.cpp /Users/zaphoyd/software/boost_1_55_0/stage/lib/libboost_system.a

clang++ -I/Users/zaphoyd/software/websocketpp/ -I/Users/zaphoyd/software/boost_1_55_0/ step4.cpp /Users/zaphoyd/software/boost_1_55_0/stage/lib/libboost_system.a /Users/zaphoyd/software/boost_1_55_0/stage/lib/libboost_thread.a /Users/zaphoyd/software/boost_1_55_0/stage/lib/libboost_random.a

clang++ -std=c++11 -stdlib=libc++ -I/Users/zaphoyd/Documents/websocketpp/ -I/Users/zaphoyd/Documents/boost_1_53_0_libcpp/ -D_WEBSOCKETPP_CPP11_STL_ step4.cpp /Users/zaphoyd/Documents/boost_1_53_0_libcpp/stage/lib/libboost_system.a

*/
